// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © LonesomeTheBlue

// remove oldest
Truncate(float [] zigzag, int mLoc)=> 
    for x = array.size(zigzag) - 1 to array.size(zigzag) > 1 ? 0 : na by 2
        if bar_index - array.get(zigzag, x) <= mLoc
            break
        array.pop(zigzag)
        array.pop(zigzag)

// add new point zigzag
addtozigzag(float [] zigzag, float value) =>
    array.unshift(zigzag, bar_index)
    array.unshift(zigzag, value)

// update zigzag
updatezigzag(float [] zigzag, float value, int dir) =>
    if array.size(zigzag) == 0
        addtozigzag(zigzag, value)
    else
        if dir == 1 and value > array.get(zigzag, 0) or dir == -1 and value < array.get(zigzag, 0)
            array.set(zigzag, 0, value)
            array.set(zigzag, 1, bar_index)

// get the zigzag in an array
getZigzag(float [] zigzag, int prd)=>
    Truncate(zigzag, 500)
    float ph = ta.highestbars(high, prd) == 0 ? high : na
    float pl = ta.lowestbars(low, prd) == 0 ? low : na
    var int dir = 0
    dir := not na(ph) and na(pl) ? 1 :
           not na(pl) and na(ph) ? -1 : 
           dir
    bool dirchanged = dir != dir[1]
    if not na(ph) or not na(pl)
        if dirchanged  //and not nz(bothexist[1], false)
            addtozigzag(zigzag, dir == 1 ? ph : pl)
        else
            updatezigzag(zigzag, dir == 1 ? ph : pl, dir)
    dir

//@version=6
indicator("Wolfe Wave Pattern", overlay = true, max_lines_count = 500, max_bars_back = 501)
baseprd = input.int(defval=5, title='Base Period', minval=2, maxval=50, tooltip='Minimum Zigzag Period to create new Zigzag wave')
error = input.float(defval=40, title='Error rate%', step = 5, minval = 5, maxval = 100, tooltip = "Deviation")
fillpattern = input.bool(defval = true, title = "Colored Patterns", inline = "colors")
bullcolor = input.color(defval = color.new(color.lime, 70), title = "", inline = "colors")
bearcolor = input.color(defval = color.new(color.red, 70), title = "", inline = "colors")
showlabels = input.bool(defval = true, title = "Labels")
linecolor = input.color(defval = color.blue, title = "Line color", inline = "line")

// deviation
var float maxerror = 1 + error / 100
var float minerror = 1 - error / 100
    
// get zigzag
var zigzag = array.new_float(0)
dir = getZigzag(zigzag, baseprd)
dirchanged = dir != dir[1]

// find bullish wolfe waves
findcandidatesbull(float[] zigzag)=>
    secondone = array.from(2)
    candidates = matrix.new<int>()
    // find suitable 5 points if there is any
    highest = zigzag.get(2)
    for x = 6 to zigzag.size() - 1 by 4
        if x >= zigzag.size() - 10
            break
        if zigzag.get(x - 2) < zigzag.get(0)
            break 
        if zigzag.get(x) > highest
            highest := zigzag.get(x)
            secondone.push(x)
    // search 3-2-1 waves
    for x = 0 to secondone.size() - 1
        prey = zigzag.get(secondone.get(x) + 4)
        for y = secondone.get(x) + 4 to zigzag.size() - 3 by 4
            if prey > zigzag.get(y)
                break
            lowest = zigzag.get(secondone.get(x) + 2)
            lowindex = secondone.get(x) + 2 
            higherexist = false
            for i = secondone.get(x) + 2 to y - 2 by 2
                if zigzag.get(i) > zigzag.get(secondone.get(x) )
                    higherexist := true
                    break
                if zigzag.get(i) < lowest
                    lowest := zigzag.get(i)
                    lowindex := i
            if higherexist
                break
            prelow = zigzag.get(y + 2)
            for i = y + 2 to zigzag.size() - 1 by 4
                if zigzag.get(i) > prelow or zigzag.get(i - 2) > zigzag.get(y) 
                    break
                prelow := zigzag.get(i)
                candidates.add_row(candidates.rows(), array.from(secondone.get(x), lowindex, y, i))
            prey := zigzag.get(y)

    // remove unsuitable candidates
    for x = candidates.rows() > 0 ? candidates.rows() - 1 : na to 0
        dist1 = zigzag.get(candidates.get(x, 0) + 1) - zigzag.get(candidates.get(x, 2) + 1)
        dist2 = zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1)
        // check if 1-3-5 symmetrical
        if zigzag.get(1) - zigzag.get(candidates.get(x, 1) + 1) > (zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * maxerror or 
           zigzag.get(1) - zigzag.get(candidates.get(x, 1) + 1) < (zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * minerror
            candidates.remove_row(x)
        // check if the shape is symmetrical
        else if  zigzag.get(candidates.get(x, 0)) - zigzag.get(candidates.get(x, 1)) >= zigzag.get(candidates.get(x, 2)) - zigzag.get(candidates.get(x, 3)) 
                 or zigzag.get(candidates.get(x, 0)) >= zigzag.get(candidates.get(x, 2)) 
                 or zigzag.get(candidates.get(x, 0)) <= zigzag.get(candidates.get(x, 3))
            candidates.remove_row(x)
        // check if the shape is symmetrical
        else if  zigzag.get(candidates.get(x, 0) + 1) - zigzag.get(candidates.get(x, 1) + 1) > (zigzag.get(candidates.get(x, 2) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * maxerror or 
                 zigzag.get(candidates.get(x, 0) + 1) - zigzag.get(candidates.get(x, 1) + 1) < (zigzag.get(candidates.get(x, 2) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * minerror
            candidates.remove_row(x)
        else if (zigzag.get(candidates.get(x, 2)) - zigzag.get(candidates.get(x, 0))) / dist1 < (zigzag.get(candidates.get(x, 3)) - zigzag.get(candidates.get(x, 1))) / dist2
            candidates.remove_row(x)
        else // check if the line between 1-3 is below 5
            float step = (zigzag.get(candidates.get(x, 1)) - zigzag.get(candidates.get(x, 3))) / (zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1))
            float level5 = zigzag.get(candidates.get(x, 3)) + step * (zigzag.get(1) - zigzag.get(candidates.get(x, 3) + 1))
            if zigzag.get(0) > level5
                candidates.remove_row(x)
    candidates

// find bearish wolfe waves
findcandidatesbear(float[] zigzag)=>
    secondone = array.from(2)
    candidates = matrix.new<int>()
    lowest = zigzag.get(2)
    for x = 6 to zigzag.size() - 1 by 4
        if x >= zigzag.size() - 10
            break
        if zigzag.get(x - 2) > zigzag.get(0)
            break 
        if zigzag.get(x) < lowest
            lowest := zigzag.get(x)
            secondone.push(x)
    
    for x = 0 to secondone.size() - 1
        prey = zigzag.get(secondone.get(x) + 4)
        for y = secondone.get(x) + 4 to zigzag.size() - 3 by 4
            if prey < zigzag.get(y)
                break
            highest = zigzag.get(secondone.get(x) + 2)
            highindex = secondone.get(x) + 2 
            lowerexist = false
            for i = secondone.get(x) + 2 to y - 2 by 2
                if zigzag.get(i) < zigzag.get(secondone.get(x) )
                    lowerexist := true
                    break
                if zigzag.get(i) > highest
                    highest := zigzag.get(i)
                    highindex := i
            if lowerexist
                break
            prehigh = zigzag.get(y + 2)
            for i = y + 2 to zigzag.size() - 1 by 4
                if zigzag.get(i) < prehigh or zigzag.get(i - 2) < zigzag.get(y)
                    break
                prehigh := zigzag.get(i)
                candidates.add_row(candidates.rows(), array.from(secondone.get(x), highindex, y, i))
            prey := zigzag.get(y)

    // remove unsuitable
    for x = candidates.rows() > 0 ? candidates.rows() - 1 : na to 0
        dist1 = zigzag.get(candidates.get(x, 0) + 1) - zigzag.get(candidates.get(x, 2) + 1)
        dist2 = zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1)
        if zigzag.get(1) - zigzag.get(candidates.get(x, 1) + 1) > (zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * maxerror or 
           zigzag.get(1) - zigzag.get(candidates.get(x, 1) + 1) < (zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * minerror
            candidates.remove_row(x)
        else if  zigzag.get(candidates.get(x, 1)) - zigzag.get(candidates.get(x, 0)) >= zigzag.get(candidates.get(x, 3)) - zigzag.get(candidates.get(x, 2)) 
                 or zigzag.get(candidates.get(x, 0)) >= zigzag.get(candidates.get(x, 3)) 
                 or zigzag.get(candidates.get(x, 0)) <= zigzag.get(candidates.get(x, 2))
            candidates.remove_row(x)
        else if (zigzag.get(candidates.get(x, 0)) - zigzag.get(candidates.get(x, 2))) / dist1 < (zigzag.get(candidates.get(x, 1)) - zigzag.get(candidates.get(x, 3))) / dist2
            candidates.remove_row(x)
        else if  zigzag.get(candidates.get(x, 0) + 1) - zigzag.get(candidates.get(x, 1) + 1) > (zigzag.get(candidates.get(x, 2) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * maxerror or 
                 zigzag.get(candidates.get(x, 0) + 1) - zigzag.get(candidates.get(x, 1) + 1) < (zigzag.get(candidates.get(x, 2) + 1) - zigzag.get(candidates.get(x, 3) + 1)) * minerror
            candidates.remove_row(x)
        else // check if the line between 1-3 is below 5
            float step = (zigzag.get(candidates.get(x, 1)) - zigzag.get(candidates.get(x, 3))) / (zigzag.get(candidates.get(x, 1) + 1) - zigzag.get(candidates.get(x, 3) + 1))
            float level5 = zigzag.get(candidates.get(x, 3)) + step * (zigzag.get(1) - zigzag.get(candidates.get(x, 3) + 1))
            if zigzag.get(0) < level5
                candidates.remove_row(x)
    candidates

// draw last wolfe wave is there is more than 1 (last one is the biggest one)
drawwolfe(matrix<int> wolfes, float[] zigzag, color col, color col2, matrix<float> targets)=>
    // get last pattern from the matrix
    wolfe = wolfes.row(wolfes.rows() - 1)
    l1 = line.new(x1 = math.round(zigzag.get(1)), y1 = zigzag.get(0), x2 = math.round(zigzag.get(wolfe.get(0) + 1)), y2 = zigzag.get(wolfe.get(0)), color = col2),
    l2 = line.new(x1 = math.round(zigzag.get(wolfe.get(1) + 1)), y1 = zigzag.get(wolfe.get(1)), x2 = math.round(zigzag.get(wolfe.get(0) + 1)), y2 = zigzag.get(wolfe.get(0)), color = col2)
    l3 = line.new(x1 = math.round(zigzag.get(wolfe.get(2) + 1)), y1 = zigzag.get(wolfe.get(2)), x2 = math.round(zigzag.get(wolfe.get(1) + 1)), y2 = zigzag.get(wolfe.get(1)), color = col2),
    l4 = line.new(x1 = math.round(zigzag.get(wolfe.get(2) + 1)), y1 = zigzag.get(wolfe.get(2)), x2 = math.round(zigzag.get(wolfe.get(3) + 1)), y2 = zigzag.get(wolfe.get(3)), color = col2)
    if fillpattern
        linefill.new(l1, l2, col)
        linefill.new(l3, l4, col)
 
    // find connection point, draw target line
    h = zigzag.get(wolfe.get(2)) - zigzag.get(wolfe.get(0))
    l = zigzag.get(wolfe.get(0) + 1) - zigzag.get(wolfe.get(2) + 1)
    float slope1 = h / l
    h := zigzag.get(wolfe.get(3)) - zigzag.get(wolfe.get(1))
    l := zigzag.get(wolfe.get(1) + 1) - zigzag.get(wolfe.get(3) + 1)
    float slope2 = h / l
    float ulevel = na, float dlevel = na
    float s1 = na, float s2 = na
    if zigzag.get(wolfe.get(0)) > zigzag.get(wolfe.get(1)) // bullish
        ulevel := zigzag.get(wolfe.get(0)) - slope1
        dlevel := zigzag.get(wolfe.get(1)) - slope2
        dlevel -= (zigzag.get(wolfe.get(0) + 1) - zigzag.get(wolfe.get(1) + 1)) * slope2
        s1 := slope1, s2 := slope2
    else 
        ulevel := zigzag.get(wolfe.get(1)) - slope2
        dlevel := zigzag.get(wolfe.get(0)) - slope1
        ulevel -= (zigzag.get(wolfe.get(0) + 1) - zigzag.get(wolfe.get(1) + 1)) * slope2
        s1 := slope2, s2 := slope1
    bindex = zigzag.get(wolfe.get(0) + 1) + 1
    
    while ulevel > dlevel
        ulevel -= s1
        dlevel -= s2
        bindex += 1
    // don't extend the lines if it's connection is too far
    if bindex - bar_index < 200
        line.new(x1 = math.round(zigzag.get(wolfe.get(2) + 1)), y1 = zigzag.get(wolfe.get(2)), x2 = math.round(bindex), y2 = ulevel, color = linecolor, style = line.style_dotted)
        line.new(x1 = math.round(zigzag.get(wolfe.get(3) + 1)), y1 = zigzag.get(wolfe.get(3)), x2 = math.round(bindex), y2 = ulevel, color = linecolor, style = line.style_dotted)
    else
        line.new(x1 = math.round(zigzag.get(wolfe.get(2) + 1)), y1 = zigzag.get(wolfe.get(2)), x2 = math.round(zigzag.get(wolfe.get(0) + 1)), y2 = zigzag.get(wolfe.get(0)), color = linecolor, style = line.style_dotted)
        line.new(x1 = math.round(zigzag.get(wolfe.get(3) + 1)), y1 = zigzag.get(wolfe.get(3)), x2 = math.round(zigzag.get(wolfe.get(0) + 1)), y2 = zigzag.get(wolfe.get(0)), color = linecolor, style = line.style_dotted)
    // calculate the target line and slope
    slope = (zigzag.get(wolfe.get(3)) - zigzag.get(wolfe.get(0))) / (zigzag.get(wolfe.get(0) + 1) - zigzag.get(wolfe.get(3) + 1))
    level = (zigzag.get(1) - zigzag.get(wolfe.get(0) + 1)) * slope
    line.new(x1 = math.round(zigzag.get(wolfe.get(3) + 1)), y1 = zigzag.get(wolfe.get(3)), x2 = math.round(zigzag.get(1)), y2 = zigzag.get(wolfe.get(0)) - level, color = linecolor, style = line.style_dashed)
    // keep pattern target
    targets.add_row(0, array.from(zigzag.get(wolfe.get(3)), zigzag.get(wolfe.get(3) + 1), zigzag.get(wolfe.get(0)), zigzag.get(wolfe.get(0) + 1), slope))

// check if price hits any of the targets
checktargets(matrix<float> targets)=>
    for x = targets.rows() > 0 ? targets.rows() - 1 : na to 0
        dist = bar_index - targets.get(x, 3)
        // remove if it's olf pattern
        if dist > 1000
            targets.remove_row(x)
            continue
        level = targets.get(x, 2) - dist * targets.get(x, 4)
        if level >= low and level <= high
            line.new(x1 = math.round(targets.get(x, 3)), y1 = targets.get(x, 2), x2 = bar_index, y2 = level, color = linecolor, style = line.style_dashed)
            alert("Reached target: " + str.tostring(level), alert.freq_once_per_bar)
            targets.remove_row(x)

var targets = matrix.new<float>()
var nopattern = true
// check if there is enough waves
if zigzag.size() >= 12
    // check if any of the targets were hit
    checktargets(targets)
    if dirchanged
        nopattern := true
    // chech if there is no active pattern on the same wave
    if dir == -1 and nopattern
        // check bullish wolfe waves
        wolfes = findcandidatesbull(zigzag)
        if wolfes.rows() > 0
            if showlabels
                label.new(bar_index, low, "Bullish", style = label.style_label_up, color = bullcolor, textcolor = color.white, size = size.small)
            drawwolfe(wolfes, zigzag, bullcolor, color.lime, targets)
            alert(str.tostring(syminfo.tickerid) + " Bullish Wolfe Wave Found!", alert.freq_once_per_bar_close)
            nopattern := false
    else if dir == 1 and nopattern
        // check bearish wolfe waves
        wolfes = findcandidatesbear(zigzag)
        if wolfes.rows() > 0
            if showlabels
                label.new(bar_index, high, "Bearish", color = bearcolor, textcolor = color.white, size = size.small)
            drawwolfe(wolfes, zigzag, bearcolor, color.red, targets)
            alert(str.tostring(syminfo.tickerid) + " Bearish Wolfe Wave Found!", alert.freq_once_per_bar_close)
            nopattern := false
